//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------
 
using System.Collections.Generic;
using System;
using System.Collections;

using Flare.Display;
using Flare.Text;

namespace Flare.Sys
{
    /// <summary>
    /// The ApplicationDomain class maintains definitions exported from SWF files. They
    /// permit multiple definitions of exported assets to exist. All exported assets
    /// loaded from a SWF file are stored in an application domain. The current application
    /// domain is the one associated with the native window that is currently activated for
    /// scripts, by default any loaded content will be placed in this domain. Every application
    /// domain has an associated parent domain except for the system domain, which is an
    /// ancestor of all application domains. Assets exported from SWF files in a child
    /// application domain override the definitions found in parent domains.
    /// </summary>
    public class ApplicationDomain
    {
        private static ApplicationDomain ms_systemDomain = new ApplicationDomain();

        /// <summary>
        /// Gets the system application domain
        /// </summary>
        public static ApplicationDomain systemDomain { get { return ms_systemDomain; } }

        /// <summary>
        /// Gets the application domain associated with the currently active native window
        /// </summary>
        public static ApplicationDomain currentDomain {
            get
            {
                if (NativeWindow.windowActiveForScripts != null)
                {
                    return NativeWindow.windowActiveForScripts.applicationDomain;
                }
                else
                {
                    // NativeWindow.windowActiveForScripts should never be null
                    throw new System.InvalidOperationException(
                        "ActivateForScripts must be called on a NativeWindow prior to accessing " +
                        "the current application domain.");
                }
            }
        }

        /// <summary>
        /// Gets the parent application domain of this application domain
        /// </summary>
        public ApplicationDomain parentDomain { get; 
            /* \cond */ private set; /* \endcond */ }

        /// <summary>
        /// Repository of exported symbols from a loaded SWF file
        /// </summary>
        private Dictionary<string, object> exportedSymbols { get; set; }

        /// <summary>
        /// Repository of embedded fonts loaded from SWF files
        /// </summary>
        private Dictionary<string, EmbeddedFont> embeddedFonts { get; set; }

        /// <summary>
        /// Initializes a new instance of the ApplicationDomain class. If no parent domain
        /// is specified then the system domain is used as the parent.
        /// </summary>
        public ApplicationDomain(ApplicationDomain parentDomain = null)
        {
            if (parentDomain == null)
            {
                this.parentDomain = ms_systemDomain;
            }
            else
            {
                this.parentDomain = parentDomain;
            }

            // Create the exported symbol dictionary
            this.exportedSymbols = new Dictionary<string, object>();

            // Create embedded font dictionary
            this.embeddedFonts = new Dictionary<string, EmbeddedFont>();
        }

        /// <summary>
        /// Returns true if a definition exists within the application domain for
        /// the specified name.
        /// </summary>
        public bool HasDefinition(string name)
        {
            if (this.exportedSymbols.ContainsKey(name))
            {
                return true;
            }
            else if (this.parentDomain != null)
            {
                return this.parentDomain.HasDefinition(name);
            }
            else
            {
                return false;
            }
        }
        
        /// <summary>
        /// Returns true if a definition exists within the application domain for
        /// the specified name.
        /// @deprecated Use HasDefinition() instead.
        /// </summary>
        [System.Obsolete("Use HasDefinition() instead.")]
        public bool hasDefinition(string name)
        {
            return HasDefinition(name);
        }

        /// <summary>
        /// Return an array of all the symbol definition names exported in the SWF file.
        /// </summary>
        public string[] GetNames()
        {
            List<string> definitions = new List<string>();

            // Enumerates through the SWF asset file for all of the SWF symbols
            foreach (KeyValuePair<string,object> swfObject in exportedSymbols)
            {
                definitions.Add(swfObject.Key);
            }

            return definitions.ToArray();
        }

        public object GetDefinition(string name)
        {
            return exportedSymbols[name];
        }

        /// <summary>
        /// Returns a new instance of the object of type T defined within the application
        /// domain or null if the definition does not exist.
        /// </summary>
        public T InstantiateDefinition<T>(string name) where T : class, System.ICloneable
        {
            if (this.exportedSymbols.ContainsKey(name))
            {
                object definition = this.exportedSymbols[name];
                if (definition is T)
                {
                    return (T)(((T)definition).Clone());
                }
                else
                {
                    return null;
                }
            }
            else if (this.parentDomain != null)
            {
                return this.parentDomain.InstantiateDefinition<T>(name);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Returns a new instance of the object of type T defined within the application
        /// domain or null if the definition does not exist.
        /// @deprecated Use InstantiateDefinition<T>() instead.
        /// </summary>
        [System.Obsolete("Use InstantiateDefinition<T>() instead.")]
        public T instantiateDefinition<T>(string name) where T : class, System.ICloneable
        {
            return InstantiateDefinition<T>(name);
        }

        /// <summary>
        /// Returns a new instance of the object defined within the application domain or null
        /// if the definition does not exist.
        /// @deprecated Use InstantiateDefinition<T>() instead.
        /// </summary>
        [System.Obsolete("Use InstantiateDefinition<T>() instead.")]
        public DisplayObject instantiateDefinition(string name)
        {
            if (this.exportedSymbols.ContainsKey(name))
            {
                object definition = this.exportedSymbols[name];
                if (definition is DisplayObject)
                {
                    return (DisplayObject)((DisplayObject)definition).Clone();
                }
                else
                {
                    return null;
                }
            }
            else if (this.parentDomain != null)
            {
                return this.parentDomain.instantiateDefinition(name);
            }
            else
            {
                return null;
            }
        }

        internal void AddExportedSymbol(string name, object definition)
        {
            if (this.exportedSymbols.ContainsKey(name))
            {
                Log.Warning(Subsystem.LoadingGeneral, "Previous '{0}' definition is " +
                    "being overridden by another export.", name);
            }

            this.exportedSymbols.Add(name, definition);
        }

        internal void AddEmbeddedFont(string name, EmbeddedFont font)
        {
            if (this.embeddedFonts.ContainsKey(name))
            {
                Log.Warning(Subsystem.LoadingGeneral, "Previous loaded '{0}' font is " +
                            "being overridden with another loaded font.", name);
            }
            
            this.embeddedFonts.Add(name, font);
        }

        internal EmbeddedFont GetEmbeddedFont(string name)
        {
            EmbeddedFont font = null;
            this.embeddedFonts.TryGetValue(name, out font);
            return font;
        }
    }
}
